# coding=utf-8
from datetime import datetime
import traceback
from multiprocessing import Pool
import os
import shutil
from typing import Tuple, Union, NoReturn
from unittest import TestSuite
# 本地导入
from src import Config, remove_logs
from src import logger
from src.report.HTMLTestRunner import HTMLTestRunner
from src.report.sendemail import send_email
from src.runthroughcase.runthroughcase import test_plan
from src.public.public import Public, ParseXml
from src.dataclass.report.report import ReMakeReportInfo, ReportInfo
from BasicAPI import Login


# 重制报告内容
def remake_report(results: list):
	start_time = datetime.strptime("2999-12-31 23:59:59", r"%Y-%m-%d %H:%M:%S")
	stop_time = datetime.strptime("2000-01-01 00:00:01", r"%Y-%m-%d %H:%M:%S")
	total_success_count = 0
	total_failure_count = 0
	total_error_count = 0
	total_results = []
	total_test_plan_count = len(results)  # 测试计划配置中启用的案例个数
	test_plan_results = results
	responsible_person_of_failure_or_error_result = []
	for i, _results in enumerate(results):
		total_testsuit_count = len(_results)  # 测试案例中配置的并发案例个数(即Excel中sheet个数)
		for j, result in enumerate(_results):
			dt_start_time = result.start_time  # result为_NewTestResult对象
			dt_stop_time = result.stop_time
			start_time = dt_start_time if dt_start_time < start_time else start_time
			stop_time = dt_stop_time if dt_stop_time > stop_time else stop_time
			total_success_count += result.success_count
			total_failure_count += result.failure_count
			total_error_count += result.error_count
			total_results.extend(result.result)
			responsible_person_of_failure_or_error_result.extend(result.responsible_person_of_failure_or_error_result)
	ReMakeReportInfo.start_time = start_time
	ReMakeReportInfo.stop_time = stop_time
	ReMakeReportInfo.success_count = total_success_count
	ReMakeReportInfo.failure_count = total_failure_count
	ReMakeReportInfo.error_count = total_error_count
	ReMakeReportInfo.title = Config.title
	ReMakeReportInfo.description = Config.description
	ReMakeReportInfo.test_method_results = total_results
	ReMakeReportInfo.test_plan_results = test_plan_results
	ReMakeReportInfo.responsible_person_of_failure_or_error_result = \
		list(set(responsible_person_of_failure_or_error_result))
	ReMakeReportInfo.environment = Config.base_url


# 写报告
def write_report() -> Union[str, NoReturn]:
	# 如果配置不生成报告则直接返回None
	if not Config.gen_report:
		return
	# 报告相关信息
	report_parent_dir = ReportInfo.report_parent_dir
	report_path = ReportInfo.report_path
	os.mkdir(report_parent_dir)
	with open(report_path, "wb") as fp:
		html_test_runner = HTMLTestRunner(stream=fp, title=Config.title, description=Config.description)
		html_test_runner.start_time = ReMakeReportInfo.start_time
		html_test_runner.stop_time = ReMakeReportInfo.stop_time
		html_test_runner.title = ReMakeReportInfo.title
		html_test_runner.description = ReMakeReportInfo.description
		html_test_runner.generate_report(test=None, result=ReMakeReportInfo)
	# 2018年10月10日 zz：将projectDir/src/css&js/下资源文件copy到报告html相同目录下
	src_css = os.path.join(Config.project_dir, "src", "css&js", "bootstrap.min.css")
	src_js = os.path.join(Config.project_dir, "src", "css&js", "echarts.common.min.js")
	shutil.copy2(src=src_css, dst=report_parent_dir)
	shutil.copy2(src=src_js, dst=report_parent_dir)
	archive_file_name = Public.make_archive(
		base_name=report_parent_dir,
		for_mat="zip",
		root_dir=report_parent_dir
	)  # 压缩报告
	return archive_file_name


# 将报告复制到web/app/static/report目录下
def copy_report():
	if Config.backups:
		src = os.path.join(Config.project_dir, r'Report')
		dst = os.path.join(Config.project_dir, r'src/web/app/static/report')
		if os.path.exists(dst):
			shutil.rmtree(dst)
		if os.path.exists(src):
			shutil.copytree(src=src, dst=dst)


# 构造测试集
def make_test_case() -> Tuple[Tuple]:
	try:
		logger.info("构建测试用例开始")
		# 按照Excel配置运行
		all_suites = test_plan()
		logger.info("构建测试用例完毕")
		return all_suites
	except Exception:
		logger.error("测试用例构建失败。报错信息:\n" + traceback.format_exc())


# 每个进程运行方法
def run(suite: TestSuite = None):
	# 2018年9月6日 zz：加上进程号
	# pid = current_process().pid  # 当前进程号
	# 不生成测试报告运行
	# runner = unittest.TextTestRunner()
	# runner.run(suite)
	# 生成测试报告运行
	# 2018年9月9日 zz: 增加案例执行失败异常保护
	# 2018年9月17日 zz: 调整为多进程生成一份测试报告
	result = None
	try:
		runner = HTMLTestRunner(
			# stream=fp,
			# title=Config.title,
			# description=Config.description,
			report=False  # 默认不输出报告，测试计划执行完后再重制报告并输出报告
		)
		result = runner.run(suite)
	except Exception:
		logger.error("案例执行失败。报错信息:\n" + traceback.format_exc())
	return result


# 运行前的初始化工作
def init():
	try:
		logger.info("自动化执行前初始化开始")
		# 1. 检查是否存在未执行完毕的进程
		xml = ParseXml(filepath=os.path.join(Config.project_dir, "automation.data"))
		running_pid = int(xml.get_value_from_xpath(xpath="./BuildData/RunningPID", attribute="pid"))
		if running_pid != -1:
			from stoptest import stop  # 防止循环引用，放到执行时导入
			stop()
		# 2. 检查目录
		temp_img_path = os.path.join(Config.project_dir, "Report", "tempimg")  # 截图的临时目录
		if os.path.exists(temp_img_path):
			shutil.rmtree(temp_img_path)
			os.mkdir(temp_img_path)
		else:
			os.mkdir(temp_img_path)
		log_dir = os.path.join(Config.project_dir, "Log")  # 日志目录
		if not os.path.exists(log_dir):
			os.mkdir(log_dir)
		# 3. 重置数据
		xml = ParseXml(filepath=os.path.join(Config.project_dir, "automation.data"))
		build_num = xml.get_value_from_xpath(xpath="./BuildData/BuildCount", attribute="count")
		build_num = int(build_num) + 1
		xml.set_value_from_xpath(xpath="./BuildData/BuildCount", attribute="count", value=str(build_num))
		date, time = ReportInfo.time.split("_")
		xml.set_value_from_xpath(xpath="./BuildData/LastBuildDate", attribute="date", value=date)
		xml.set_value_from_xpath(xpath="./BuildData/LastBuildDate", attribute="time", value=time)
		xml.set_value_from_xpath(xpath="./BuildData/RunningPID", attribute="pid", value=str(os.getpid()))
		xml.set_value_from_xpath(xpath="./BuildData/ExecProgress", attribute="total", value="0")
		xml.set_value_from_xpath(xpath="./BuildData/ExecProgress", attribute="executed", value="0")
		# 4. 删除过期日志
		remove_logs()
		# 5. 读取LoginData文件登录获取cookie和Authorization信息
		Login().login_1()
		logger.info("自动化执行前初始化完毕")
	except Exception:
		logger.error("自动化执行前初始化失败。报错信息:\n" + traceback.format_exc())


# 发送报告
# 邮件发送单独拿到一个函数中
def send_report(attch_path: str, info: ReMakeReportInfo):
	"""
	:param attch_path: 附件路径
	:param info: 报告信息，用于邮件正文展示关键信息
	"""
	# 邮件发送
	try:
		if Config.send_email:
			logger.info("邮件发送开始")
			send_email(attch_path, info)  # 发送邮件
			send_email(attch_path, info, flag='RESPONSIBLE_PERSON_EMAIL')  # 发送邮件
			logger.info("邮件发送结束")
	except Exception:
		logger.error("邮件发送失败。报错信息:\n" + traceback.format_exc())


# # 钉钉消息通知
# def send_notification():
#     try:
#         if Config.notifier_enable:
#             logger.info("开始消息通知")
#             msg = "项目名称: %s\n开始时间: %s\n结束时间: %s\n成功个数: %s\n失败个数: %s\n错误个数: %s\n成功率: %.2f%%\n" % (
#                 ReMakeReportInfo.title,
#                 ReMakeReportInfo.start_time,
#                 ReMakeReportInfo.stop_time,
#                 ReMakeReportInfo.success_count,
#                 ReMakeReportInfo.failure_count,
#                 ReMakeReportInfo.error_count,
#                 ReMakeReportInfo.success_count * 100 / (ReMakeReportInfo.failure_count +
#                                                         ReMakeReportInfo.error_count +
#                                                         ReMakeReportInfo.success_count)
#             )
#             response = send_text_msg(msg)  # type: Dict[str, int]
#             logger.info("消息通知结束")
#             if response["errcode"] != 0:
#                 logger.error("消息通知失败。报错信息:\n" + str(response))
#     except Exception:
#         logger.error("消息通知失败。报错信息:\n" + traceback.format_exc())
#

def update_aborted_data():
	xml = ParseXml(filepath=os.path.join(Config.project_dir, "automation.data"))
	xml.set_value_from_xpath(xpath='./BuildData/RunningPID', attribute='pid', value='-1')


def register_total_testcases_count(all_suites: Tuple[Tuple]) -> NoReturn:
	"""登记总共案例个数"""
	total = 0
	for suites in all_suites:
		for suite in suites:
			total += len(list(suite))
	xml = ParseXml(filepath=os.path.join(Config.project_dir, "automation.data"))
	xml.set_value_from_xpath(xpath='./BuildData/ExecProgress', attribute='total', value=str(total))


def main():
	# 运行前的初始化工作
	init()
	# 构造并返回测试集1
	all_suites = make_test_case()  # Tuple[Tuple(TestSuite)]
	# 记录总共案例个数
	register_total_testcases_count(all_suites)
	# 多进程执行开始
	logger.info("测试执行开始")
	# 2018年9月6日 zz: 修改使用pool进程池，使得每个独立的进程可以返回执行结果。
	results = []  # 结果集
	# 遍历测试套件, 并使用多进程执行(for 循环为串行，suites为并行)
	for suites in all_suites:  # all_suites Tuple[Tuple(TestSuite)]
		pool = Pool(len(suites) + 1)
		logger.info("执行测试套件：" + "".join([suite.__repr__() for suite in suites]))
		rl = pool.map(func=run, iterable=suites)  # 返回run方法的返回值 # suites内的测试案例在多进程执行时，logger记录日志会记录在一个文件。
		pool.close()  # 关闭进程池，不再接受新的进程
		pool.join()  # 主进程阻塞等待子进程的退出
		results.append(rl)
	# 重制报告内容
	remake_report(results=results)
	# 发送通知
	# send_notification()
	# 写报告
	archive_file_name = write_report()
	# 将报告复制到web/app/static/report目录下
	copy_report()
	logger.info("测试执行结束")
	# 发送报告
	send_report(attch_path=archive_file_name, info=ReMakeReportInfo)


# 执行测试集
if __name__ == "__main__":
	try:
		main()
	except BaseException:
		logger.error('构建异常终止。错误原因:\n' + traceback.format_exc())
	finally:
		# 更新终止状态数据
		update_aborted_data()
